package com.uxsino.simo.collector.connections;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Strings;
import com.uxsino.simo.connections.AbstractConnection;
import com.uxsino.simo.connections.exception.SimoConnectionException;
import com.uxsino.simo.connections.target.AgentTarget;
import lombok.Data;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Map;

/**
 * @ClassName AgentConnection
 * @Description TODO
 * @Author <a href="mailto:royrxc@gmail.com">Ran</a>
 * @Daate 2019/10/24 10:13
 **/
public class AgentConnection extends AbstractConnection<AgentTarget> {
    static Logger logger = LoggerFactory.getLogger(HTTPConnection.class);
    private CloseableHttpClient client;
    private String urlBase;
    private static int DEFAULT_TIMEOUT_MS = 10000;
    private static int DEFAULT_TIMEOUT_CONNECT_MS = 2000;

    static {
        System.setProperty("http.maxConnections", "5000");
    }

    @Override
    public int connect(AgentTarget target) {
        this.target = target;
        super.connect(target);
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY,
                new UsernamePasswordCredentials(target.getUsername(), target.getPassword()));
        HttpClientBuilder builder = HttpClientBuilder.create();
        builder.setDefaultCredentialsProvider(credentialsProvider);
        client = builder.build();
        urlBase = "http://" + target.host + ":" + target.port;
        connected = true;   // not really connected
        state = 1;
        return state;
    }

    private Object exec(Object cmdPattern) throws SimoConnectionException {
        if(AgentTarget.FETCH_TYPE.PUSH.equals(this.target.getFetchType())){
            logger.info("协议使用推送的方式进行数据采集，忽略采集。。。。");
            return null;
        }

        String cmdStr = null;
        if(AgentTarget.ROUTE_TYPE.ROUTE.equals(target.getExecType())){
            if(Strings.isNullOrEmpty(target.getRouterUrl())){
                logger.warn("----------------agent route url not config----------------");
                return null;
            }
            cmdStr = target.getRouterUrl()+"?username="+(Strings.nullToEmpty(target.getRouteUser())+"&password="+Strings.nullToEmpty(target.getRoutePwd()));
        }else{
            cmdStr = urlBase+"/main/exec?username="+(Strings.nullToEmpty(target.getUsername())+"&password="+Strings.nullToEmpty(target.getPassword())+(Strings.isNullOrEmpty(target.getEncoding())?"":("&encoding="+target.getEncoding())));
        }

        HttpPost post = new HttpPost(cmdStr);

        int timeoutMs = target.getTimeout()<DEFAULT_TIMEOUT_MS?DEFAULT_TIMEOUT_MS:target.getTimeout();

        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(timeoutMs)//获取数据的超时时间
                .setConnectTimeout(DEFAULT_TIMEOUT_CONNECT_MS)//建立连接的超时时间
                .setConnectionRequestTimeout(timeoutMs)//数据传输超时时间
                .build();
        post.setConfig(requestConfig);
        post.setHeader("Content-Type", "application/json;charset=UTF-8");
        if(AgentTarget.ROUTE_TYPE.ROUTE.equals(target.getExecType())){
            RouteCmd cmd = new RouteCmd();
            cmd.setIp(target.host);
            cmd.setPort(target.port);
            cmd.setUsername(target.getUsername());
            cmd.setPassword(target.getPassword());
            cmd.setCmd((String)cmdPattern);
            cmd.setEncoding(target.getEncoding());
            post.setEntity(new ByteArrayEntity(JSON.toJSONString(cmd).getBytes(Charset.defaultCharset())));
        }else{
            if(cmdPattern != null){
                post.setEntity(new ByteArrayEntity(buildCmd(cmdPattern.toString(), null).getBytes(Charset.defaultCharset())));
            }
        }

        HttpEntity entity = null;
        try {
            HttpResponse response = client.execute(post);
            entity = response.getEntity();
            InputStream in = entity.getContent();

            ByteArrayOutputStream resultByte = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length;
            while ((length = in.read(buffer)) != -1) {
                resultByte.write(buffer, 0, length);
            }
            String result = resultByte.toString("UTF-8");
            try {
                JSONObject res = JSON.parseObject(result);
                return res;
            }catch (Exception e){
                logger.warn("{}", e);
                return null;
            }

        } catch (IOException e) {
            logger.error("error executing cmd in http connection", e);
            throw new SimoConnectionException(e);
        } finally {
            EntityUtils.consumeQuietly(entity);
        }
    }

    @Override
    public Object execCmd(Object cmdPattern) throws SimoConnectionException {
        JSONObject res = (JSONObject)exec(cmdPattern);
        if(res == null){
            return null;
        }
        Integer code = res.getInteger("code");
        if(code == null){
            return null;
        }
        if (code.equals(new Integer(0))) {
            return res.get("data");
        }else{
            logger.warn("get agent data error with error code : {}, msg: {}, {}", code, res.get("msg"), res.get("data"));
            return null;
        }
    }

    @Override
    public String buildCmd(String cmdPattern, Map<String, String> args) {
        return cmdPattern;
    }

    @Override
    public int close() {

        try {
            if (client != null)
                client.close();
        } catch (IOException e) {
            logger.error("error closing http connection", e);
        }
        connected = false;

        super.close();
        return 0;
    }

    @Override
    public boolean testWithConnected(String cmdString, String resStart) {
        if(AgentTarget.FETCH_TYPE.PUSH.equals(this.target.getFetchType())){
            connected = true;
            return true;
        }

        connected = false;
        try{
            JSONObject res = null;
            try {
                res = (JSONObject) exec("");
            } catch (SimoConnectionException e) {
                logger.error("error executing cmd in http connection", e);
            }
            if(res != null ){
                Long code = res.getLong("code");
                if(code.equals(-777L)){
                    connected = false;
                }else{
                    connected = true;
                }
            }
        }catch (Exception e){
            logger.warn("{}", e);
            connected = false;
        }
        logger.info("http connection test result: {}", connected);
        return connected;
    }

    @Data
    public class RouteCmd {
        String ip;
        String username;
        String password;
        int port;
        String cmd;
        String encoding;
    }

}
